package com.yumeng.framework.persistence.mybatis.plugins.dynamictable.parser;

import com.yumeng.common.exception.YumengException;
import com.yumeng.common.helper.AssertHelper;
import com.yumeng.framework.persistence.mybatis.plugins.dynamictable.DynamicTableNameMode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 动态表名处理器
 *
 * P-参数类型 id->般是Long 日期->String
 * M-模式参数 id->分几张表（Integer） 日期->日期类型（TableNameDateSuffixType）
 * @author wxd
 * @date 2022/9/2 13:53
 */
@Slf4j
public abstract class AbstractTableNameMapParser<P, M> {

    private final DynamicTableNameMode mode;

    /**
     * 哪些表可以使用这个动态表名规则
     *
     */
    private final Map<String, M> tableMap;

    protected M getTableMapVal(String key){
        return tableMap.get(key);
    }

    private final ThreadLocal<Map<String, DynamicTableNameParam>> localParam = new ThreadLocal<>();

    @Getter
    private static final List<ThreadLocal<?>>  localParamList = new ArrayList<>();

    public AbstractTableNameMapParser(Map<String, M> tableMap, DynamicTableNameMode mode){
        this.tableMap = tableMap;
        this.mode = mode;
        localParamList.add(localParam);
    }

    //此处tableCount 与 原targetCount用法不同，此处表示当前表名出现的次数。用于解决“1个sql内，同一个表名出线2次的情况”
    public void setParam(String tableName, P param, int tableCount) {
        AssertHelper.assertNotNull(param, "setParam 参数param为空");
        AssertHelper.assertIsTrue(tableCount > 0, "表次数必须大于0");
        Map<String, DynamicTableNameParam> localMap = localParam.get();
        if (localMap == null){
            localMap = new HashMap<>();
            localParam.set(localMap);
        }else{
            if (localMap.containsKey(tableName)){
                clearAllLocalParam();
                throw new YumengException("同一表名参数只能设置一次");
            }
        }
        localMap.put(tableName, new DynamicTableNameParam(tableName, tableCount, param));
    }

    private P getParam(String tableName) {
        Map<String, DynamicTableNameParam> localMap = localParam.get();
        DynamicTableNameParam paramDto;
        if (MapUtils.isEmpty(localMap) || !localMap.containsKey(tableName)){
            //当获取不到值时，为异常情况，此时需要清空所有的localParam，防止内存溢出
            clearAllLocalParam();
            throw new YumengException("【%s】表名处理器：请设置param值", mode.getDesc());
        }else{
            log.debug("getParam开始:{}", localMap);
            paramDto = localMap.get(tableName);
            int tempCount = paramDto.getTableCount() - 1;
            if (tempCount == 0){
                localMap.remove(tableName);
            } else if (tempCount < 0) {
                clearAllLocalParam();
                throw new YumengException("【%s】表名处理器：剩余映射数量异常", mode.getDesc());
            }else{
                paramDto.setTableCount(tempCount);
            }
            log.debug("getParam结束:{}", localMap);
            if (MapUtils.isEmpty(localMap)){
                localParam.remove();
                log.debug("ThreadLocal 清空:{}", localParam.get());
            }
        }
        if (paramDto.getParam() == null){
            throw new YumengException("【%s】表名处理器：请设置有效param值，当前为null", mode.getDesc());
        }
        return paramDto.getParam();
    }

    /**
     * 判断表名是否存在（该表是否应用于该处理器）
     *
     * @param tableName
     * @return
     */
    public boolean checkContains(String tableName){
        return StringUtils.isNotBlank(tableName) && tableMap != null && tableMap.containsKey(tableName);
    }

    /**
     * 清除当前线程所有参数
     */
    public static void clearAllLocalParam(){
        for (ThreadLocal<?> local : localParamList){
            log.warn("ThreadLocal:{}", local.get());
            local.remove();
            log.warn("ThreadLocal remove:{}", local.get());
        }
    }

    /**
     * 生成动态表名
     * @param sql
     * @param tableName
     * @return
     */
    public String dynamicTableName(String sql, String tableName){
        if (!checkContains(tableName)){//表名不应用于该处理器
            //因为外部已经验证过，进入此方法内的表名应该都能匹配
            log.warn("表【{}】不在【{}】表名处理器配置内", tableName, mode.getDesc());
            return tableName;
        }
        P param = getParam(tableName);
        return doDynamicTableName(sql, tableName, param);
    }

    protected abstract String doDynamicTableName(String sql, String tableName, P param);

    /**
     * 动态表名参数
     *
     */
    @Getter
    @Setter
    @AllArgsConstructor
    @ToString
    class DynamicTableNameParam{
        private String tableName;
        private int tableCount;
        private P param;
    }
}
